package com.example.demo.controller;

import java.util.Date;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import org.redisson.api.RLock;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson.JSON;
import com.example.demo.annotation.AccessLimit;
import com.example.demo.entity.Seckill;
import com.example.demo.entity.Successkilled;
import com.example.demo.model.Result;
import com.example.demo.repository.SeckillDao;
import com.example.demo.repository.SuccesskilledDao;
import com.example.demo.utils.RedissonUtils;
import com.jiangwz.utils.RedisUtil;

import lombok.extern.slf4j.Slf4j;

/**
 * @author jiangwz
 * @create 2019/10/24.
 */
@RequestMapping("/seckill")
@RestController
@Slf4j
public class SeckillController implements InitializingBean {

    @Autowired
    private SeckillDao seckillDao;
    @Autowired
    private SuccesskilledDao successkilledDao;
    @Autowired
    private RedisUtil redisUtil;

    private BlockingQueue<String> queue = new LinkedBlockingQueue<>(1000);

    /**
     * 秒杀商品,这种实现腾讯的1核mysql根本扛不住,获取连接全是超时
     *
     * @param seckill
     * @return
     */
    @PostMapping("1")
    @Deprecated
    public Object seckill1(Long seckillId, Long userId) {
        //1. 查库存,库存减一
        //2. 添加秒杀成功
        Optional<Seckill> optional = seckillDao.findById(seckillId);
        Seckill seckill1 = optional.get();
        if (seckill1.getNumber() > 0) {
            seckill1.setNumber(seckill1.getNumber() - 1);
            seckillDao.save(seckill1);

            Successkilled successkilled = Successkilled.builder().seckillId(seckillId)
                    .createTime(new Date())
                    .state(1)
                    .userId(userId).build();
            successkilledDao.save(successkilled);
            return Result.ok("秒杀成功");
        }
        return Result.error("秒杀结束");

    }

    /**
     * 使用redis方式
     *
     * @param seckillId
     * @param userId
     * @return
     */
    @AccessLimit(limit = 4, second = 10)
    @PostMapping("2")
    public Object seckill2(Long seckillId, Long userId) {
        //1. 查库存,库存减一
        //2. 添加秒杀成功
        String key = "seckill:" + seckillId;
        log.info(key);
        if (Integer.parseInt(redisUtil.get(key)) <= 0) {
            return Result.error("秒杀结束");
        }
        if (redisUtil.decrBy(key, 1) >= 0) {
            Successkilled successkilled = Successkilled.builder().seckillId(seckillId)
                    .createTime(new Date())
                    .state(1)
                    .userId(userId).build();
            queue.offer(JSON.toJSONString(successkilled));
            return Result.ok("秒杀成功");
        }
        return Result.error("秒杀结束");
    }

    private Map<Long, Boolean> localCache = new ConcurrentHashMap<>();

    @Override
    public void afterPropertiesSet() throws Exception {
        // 清空数据库
        RedissonUtils.init();
        reset();
        new Thread(() -> {
            while (true) {
                try {
                    String take = queue.take();
                    Successkilled successkilled = JSON.parseObject(take, Successkilled.class);

                    String key = "seckill:lock:" + successkilled.getSeckillId();
                    if (localCache.getOrDefault(successkilled.getSeckillId(), false)) {
                        continue;
                    }

                    /**
                     * 发现一个问题,A应用抢到锁后,之后就一直是A应用抢到锁,直到处理完所有的任务为止
                     * 这样会导致B应用等待超时的问题,导致有100个请求平均分配过来,有几个会获取锁超时,少卖几个
                     */
                    boolean tryLock = RedissonUtils.tryLock(key, TimeUnit.SECONDS, 5, 5);
                    if (tryLock) {
                        RLock lock = RedissonUtils.lock(key, TimeUnit.SECONDS, 5);
                        Optional<Seckill> optional = seckillDao.findById(successkilled.getSeckillId());
                        Seckill seckill = optional.get();
                        if (seckill.getNumber() > 0) {
//                            System.out.println("number: "+seckill.getNumber());
                            seckill.setNumber(seckill.getNumber() - 1);
                            seckillDao.save(seckill);
                            successkilledDao.save(successkilled);
                            RedissonUtils.unlock(lock);
                        }else {
                            localCache.put(seckill.getSeckillId(), true);
                        }
                    }else {
                        System.out.println("未获取到锁");
                    }
                } catch (InterruptedException e) {
                }
            }
        }).start();
    }

    private void reset() {
        Long seckillId = 1000L;
        successkilledDao.deleteAll();
        Seckill one = seckillDao.findById(seckillId).get();
        one.setNumber(100);
        seckillDao.save(one);
        String key = "seckill:" + seckillId;
        redisUtil.set(key, 100 + "");
    }
}
